﻿namespace NTLib.Test.Resources
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using NTLib.Core.Threading;
    using NTLib.Core.Extensions;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Contains helper to easily create unit test
    /// </summary>
    public class BaseTestClass
    {
        #region fields

        private static readonly SemaphoreSlim s_testlockContext = new SemaphoreSlim(1);

        #endregion

        /// <summary>
        /// Creates a timeout cancellation token used to ensure the test will not lock.
        /// If the debugger is attach not timer is used.
        /// </summary>
        /// <param name="sec">The sec.</param>
        /// <returns></returns>
        protected CancellationTokenSource CreateTimeout(int sec)
        {
            if (Debugger.IsAttached)
            {
                return new CancellationTokenSource();
            }
            else
            {
                return new CancellationTokenSource(TimeSpan.FromSeconds(sec));
            }
        }

        /// <summary>
        /// Test if the exception pass in parameter if of the type defined or contains an exception of this types.
        /// </summary>
        /// <returns>True if the test if valid; otherwise false</returns>
        protected bool ExceptionTest<T>(Exception ex)
            where T : Exception
        {
            if (ex is T)
                return true;

            if (ex.InnerException != null)
            {
                while (ex == null || ex is T)
                {
                    ex = ex.InnerException;

                    if (ex == null)
                        return false;

                    if (ex is T)
                        return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Test that the action pass in argument through the exception pass in generic parameters
        /// </summary>
        protected void AssertException<T>(Action action)
            where T : Exception
        {
            try
            {
                action();
                Assert.Fail();
            }
            catch (Exception ex)
            {
                Assert.IsTrue(ExceptionTest<T>(ex), "Expecting exception type {0} instead {1}".Argument(typeof(T), ex.GetType()));
            }
        }

        /// <summary>
        /// Test that the action pass in argument through the exception pass in generic parameters
        /// </summary>
        protected void AssertFuncException<T, TRet>(Func<TRet> action)
            where T : Exception
        {
            try
            {
                action();
                Assert.Fail();
            }
            catch (Exception ex)
            {
                Assert.IsTrue(ExceptionTest<T>(ex));
            }
        }

        /// <summary>
        /// Locks the test.
        /// To ensure any other test will not interferre in this one.
        /// </summary>
        protected IDisposable LockTest(CancellationToken token = default(CancellationToken))
        {
            return s_testlockContext.ScopeWait(token);
        }
    }
}
